﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Security;

namespace Isotop.Text
{
	public class VString
	{
		static Func<int, string> _fastAllocateString;

		static VString()
		{
			//This is somewhat of a HACK (but a power one ;) ). We use the native function FastAllocateString and in order to do that
			//we actually need to emit IL and make sure we 
			var methodInfo = typeof(string).GetMethod("FastAllocateString", BindingFlags.Static | BindingFlags.NonPublic, null, CallingConventions.Any, new[]{ typeof(int) }, null);
			
			//If not dynamic method is used, it will hang ~5th time (???)
			DynamicMethod fAllocStr = new DynamicMethod("fAllocStr", typeof(string), new[] { typeof(int) }, typeof(string).Module, true);

			ILGenerator il = fAllocStr.GetILGenerator();
			il.Emit(OpCodes.Ldarg_0);
			il.EmitCall(OpCodes.Call, methodInfo, null);
			il.Emit(OpCodes.Ret);
			fAllocStr.DefineParameter(1, ParameterAttributes.In, "length");
			
			_fastAllocateString = (Func<int, string>)fAllocStr.CreateDelegate(typeof(Func<int, string>));
		}

		readonly List<string> _strings = new List<string>();

		public static VString operator + (string str, VString vstr)
		{
			var newVstr = new VString();
			newVstr._strings.Add(str);
			newVstr._strings.AddRange(vstr._strings);
			return newVstr;
		}

		public static VString operator +(VString vstr, string str)
		{
			var nvstr = new VString();
			nvstr._strings.AddRange(vstr._strings);
			nvstr._strings.Add(str);
			return nvstr;
		}

		public void Append(string s)
		{
			if(s != string.Empty)
				_strings.Add(s);
		}

		public static implicit operator string(VString vstr) 
		{
			return vstr.ToString();
		}

		public static implicit operator VString(string str)  
		{
			var vstr = new VString();
			vstr.Append(str);
			return vstr;
		}

		[SecurityCritical]
		public unsafe override string ToString()
		{
			var output = _fastAllocateString(Length);
			int curLen = 0;
			foreach (var item in _strings)
			{
				var len = item.Length;
				fixed (char* dst = output)
				{
					fixed (char* src = item)
					{
						wstrcpy(dst + curLen, src, len);
					}
				}
				curLen += len;
			}
			return output;
		}

		public int Length
		{
			get { return _strings.Sum(s => s.Length); }
		}

		public void WriteTo(TextWriter tw)
		{
			foreach (var item in _strings)
				tw.Write(item);
		}

		public VString Substring(int index)
		{
			if (_strings.Count == 0 && index == 0)
				return string.Empty;

			int startAt = 0;
			int i;
			for (i = 0; i < _strings.Count; i++)
			{
				var len = _strings[i].Length;
				if (startAt + len > index)
					break;
				startAt += len;
			}
			var vstr = new VString();
			var iStartAt = index - startAt;

			if (iStartAt == 0)
				vstr._strings.Add(_strings[i]);
			else
				vstr._strings.Add(_strings[i].Substring(index - startAt)); //Can throw
			for (i += 1; i < _strings.Count; i++)
				vstr._strings.Add(_strings[i]);
			return vstr;
		}

	// ReSharper disable InconsistentNaming
		/// <summary>
		/// This is just copied from .NET internal implementation
		/// </summary>
		static unsafe void wstrcpy(char* dmem, char* smem, int charCount)
		{
			if (charCount > 0)
			{
				if ((((int)dmem) & 2) != 0)
				{
					dmem[0] = smem[0];
					dmem++;
					smem++;
					charCount--;
				}
				while (charCount >= 8)
				{
					*((uint*)dmem) = *((uint*)smem);
					*((uint*)(dmem + 2)) = *((uint*)(smem + 2));
					*((uint*)(dmem + 4)) = *((uint*)(smem + 4));
					*((uint*)(dmem + 6)) = *((uint*)(smem + 6));
					dmem += 8;
					smem += 8;
					charCount -= 8;
				}
				if ((charCount & 4) != 0)
				{
					*((uint*)dmem) = *((uint*)smem);
					*((uint*)(dmem + 2)) = *((uint*)(smem + 2));
					dmem += 4;
					smem += 4;
				}
				if ((charCount & 2) != 0)
				{
					*((uint*)dmem) = *((uint*)smem);
					dmem += 2;
					smem += 2;
				}
				if ((charCount & 1) != 0)
				{
					dmem[0] = smem[0];
				}
			}
		}
// ReSharper restore InconsistentNaming
	}
}